// index.ts

import axios from "axios";
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import type {
  RequestConfig,
  RequestInterceptors,
  CancelRequestSource,
  ResponseProps,
} from "./type";

// 约束后端接口不管请求成功与失败，返回的结构永远是 code、msg、data

class Request {
  // axios 实例
  instance: AxiosInstance;
  // 实例拦截器对象
  interceptorsObj?: RequestInterceptors;

  // 存放取消方法的集合
  // 在创建请求后将取消请求方法 push 到该集合中
  // 封装一个方法，可以取消请求，传入 url: string|string[]
  // 在请求之前判断同一URL是否存在，如果存在就取消请求
  cancelRequestSourceList?: CancelRequestSource[];

  // 存放所有请求URL的集合
  // 请求之前需要将url push到该集合中
  // 请求完毕后将url从集合中删除
  // 添加在发送请求之前完成，删除在响应之后删除
  requestUrlList?: string[];

  constructor(config: RequestConfig) {
    this.instance = axios.create(config);
    this.interceptorsObj = config.interceptors;
    this.requestUrlList = [];
    this.cancelRequestSourceList = [];

    // 类拦截器
    this.instance.interceptors.request.use(
      (config: AxiosRequestConfig) => {
        console.log("类请求拦截器.success");
        return config;
      },
      (error: any) => {
        // Promise.reject(err)
        console.log("类请求拦截器.error", error);
        return error;
      }
    );

    // 使用实例拦截器 - 与顺序有关
    this.instance.interceptors.request.use(
      this.interceptorsObj?.requestInterceptors,
      this.interceptorsObj?.requestInterceptorsCatch
    );
    this.instance.interceptors.response.use(
      this.interceptorsObj?.responseInterceptors,
      this.interceptorsObj?.responseInterceptorsCatch
    );

    // 类响应拦截器
    this.instance.interceptors.response.use(
      // 因为我们接口的数据都在res.data下，所以我们直接返回res.data
      (response: AxiosResponse) => {
        console.log("类响应拦截器.success", response);
        return response.data;
      },
      (error: any) => {
        console.log("类响应拦截器.error", error);
        // 这里用来处理http常见错误，进行全局提示

        let message = "";
        switch (error.response.status) {
          case 400:
            message = "请求错误(400)";
            break;
          case 401:
            message = "未授权，请重新登录(401)";
            // 这里可以做清空storage并跳转到登录页的操作
            break;
          case 403:
            message = "拒绝访问(403)";
            break;
          case 404:
            message = "请求出错(404)";
            break;
          case 408:
            message = "请求超时(408)";
            break;
          case 500:
            message = "服务器错误(500)";
            break;
          case 501:
            message = "服务未实现(501)";
            break;
          case 502:
            message = "网络错误(502)";
            break;
          case 503:
            message = "服务不可用(503)";
            break;
          case 504:
            message = "网络超时(504)";
            break;
          case 505:
            message = "HTTP版本不受支持(505)";
            break;
          default:
            message = `连接出错(${error.response.status})!`;
        }

        console.log("类响应拦截捕获错误：", message);

        // 这里错误消息可以使用全局弹框展示出来
        // 比如element plus 可以使用 ElMessage
        // ElMessage({
        //   showClose: true,
        //   message: `${message}，请检查网络或联系管理员！`,
        //   type: "error",
        // });
        // 这里是AxiosError类型，所以一般我们只reject我们需要的响应即可
        // return Promise.reject(err.response)
        return error;
      }
    );
  }
  request<T>(config: RequestConfig): Promise<T> {
    return new Promise((resolve, reject) => {
      console.log("类中request方法调用：", config);

      // 为单个请求设置请求拦截器
      if (config.interceptors?.requestInterceptors) {
        console.log("进入了吗.config.interceptors");
        config = config.interceptors.requestInterceptors(config);
      }

      const url = config.url;
      // url 存在保存取消请求方法和当前请求 url
      if (url) {
        this.requestUrlList?.push(url);
        config.cancelToken = new axios.CancelToken((c) => {
          this.cancelRequestSourceList?.push({
            [url]: c,
          });
        });
      }

      this.instance
        .request<ResponseProps<T>, T>(config)
        .then((res) => {
          console.log("类中request方法调用响应.success：", res);

          // 为单个请求设置响应拦截器
          if (config.interceptors?.responseInterceptors) {
            res = config.interceptors.responseInterceptors<T>(res);
          }

          resolve(res);
        })
        .catch((err: any) => {
          reject(err);
        })
        .finally(() => {
          url && this.delUrl(url);
        });
    });
  }

  // 删除 requestUrlList 和 cancelRequestSourceList
  private delUrl(url: string) {
    const urlIndex = this.requestUrlList?.findIndex((u) => u === url);
    const sourceIndex = this.getSourceIndex(url);
    // 删除 url 和 cancel 方法
    urlIndex !== -1 && this.requestUrlList?.splice(urlIndex as number, 1);
    sourceIndex !== -1 &&
      this.cancelRequestSourceList?.splice(sourceIndex as number, 1);
  }

  // 获取指定 url 在 cancelRequestSourceList 中的索引
  private getSourceIndex(url: string): number {
    return this.cancelRequestSourceList?.findIndex(
      (item: CancelRequestSource) => {
        return Object.keys(item)[0] === url;
      }
    ) as number;
  }

  // 取消全部请求
  cancelAllRequest() {
    this.cancelRequestSourceList?.forEach((source) => {
      const key = Object.keys(source)[0];
      source[key]();
    });
  }

  // 取消请求，可以取消一个和多个
  cancelRequest(url: string | string[]) {
    if (typeof url === "string") {
      // 取消单个请求
      const sourceIndex = this.getSourceIndex(url);
      sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][url]();
    } else {
      // 存在多个需要取消请求的地址
      url.forEach((u) => {
        const sourceIndex = this.getSourceIndex(u);
        sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][u]();
      });
    }
  }

  public get<T = any>(
    url: string,
    config?: AxiosRequestConfig
  ): Promise<AxiosResponse<ResponseProps<T>>> {
    return this.instance.get(url, config);
  }

  public post<T = any>(
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): Promise<AxiosResponse<ResponseProps<T>>> {
    return this.instance.post(url, data, config);
  }

  // public put<T = any>(
  //   url: string,
  //   data?: any,
  //   config?: AxiosRequestConfig
  // ): Promise<AxiosResponse<Result<T>>> {
  //   return this.instance.put(url, data, config);
  // }

  // public delete<T = any>(
  //   url: string,
  //   config?: AxiosRequestConfig
  // ): Promise<AxiosResponse<Result<T>>> {
  //   return this.instance.delete(url, config);
  // }
}

export default Request;
